﻿// <copyright file="HotKeyHandler.cs" company="DotNetLib">
//
// .netlib: http://www.codeplex.com/DotNetLib
//
// Portions of this were part of the CodePlex "Extensions for .NET Framework"
//    project and written by Marco Minerva, mailto:marco.minerva@gmail.com
//
// This code is released under the New BSD License.
// A copy of this license is available at:
// http://www.codeplex.com/DotNetLib/license
//
// </copyright>

namespace DotNetLib.Windows
{
  using System;
  using System.Collections.Generic;
  using System.Runtime.InteropServices;
  using System.Windows.Forms;

  /// <summary>
  /// The access modifiers of an hot-key.
  /// </summary>
  [Flags]
  public enum Modifiers
  {
    /// <summary>
    /// The hot-key doesn't use an access modifier.
    /// </summary>
    None = 0,

    /// <summary>
    /// The hot-key uses the ALT access modifier.
    /// </summary>
    Alt = 0x01,

    /// <summary>
    /// The hot-key uses the CONTROL access modifier.
    /// </summary>
    Control = 0x02,

    /// <summary>
    /// The hot-key uses the SHIFT access modifier.
    /// </summary>
    Shift = 0x04,

    /// <summary>
    /// The hot-key uses the WINDOWS KEY access modifier.
    /// </summary>
    Win = 0x08
  }

  /// <summary>
  /// <strong>HotkeyHandler</strong> allows you to define system-level hot-keys.
  /// </summary>
  public sealed class HotkeyHandler : NativeWindow, IDisposable
  {
    #region Platform Invoke

    private const int WM_HOTKEY = 0x0312;

    [DllImport("user32.dll")]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vlc);
    [DllImport("user32.dll")]
    private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    #endregion

    private struct HotKeyInfo
    {
      public Keys Key;
      public Modifiers Modifier;

      public HotKeyInfo(Keys Key, Modifiers Modifier)
      {
        this.Key = Key;
        this.Modifier = Modifier;
      }

      public static bool operator ==(HotKeyInfo hotKey1, HotKeyInfo hotKey2)
      {
        return hotKey1.Key == hotKey2.Key && hotKey1.Modifier == hotKey2.Modifier;
      }

      public static bool operator !=(HotKeyInfo hotKey1, HotKeyInfo hotKey2)
      {
        return hotKey1.Key != hotKey2.Key || hotKey1.Modifier != hotKey2.Modifier;
      }

      public override bool Equals(object obj)
      {
        if (obj == null || GetType() != obj.GetType())
        {
          return false;
        }

        return this == (HotKeyInfo)obj;
      }

      public override int GetHashCode()
      {
        return (int)this.Key ^ (int)this.Modifier;
      }
    }

    /// <summary>
    /// Event that is raised when an hot-key is pressed.
    /// </summary>
    public event EventHandler<HotkeyPressedEventArgs> HotKeyPressed;

    private static int _lastID;
    private Dictionary<int, HotKeyInfo> _hotKeys;

    /// <summary>
    /// Initializes a new instance of the <see cref="HotkeyHandler"/> class.
    /// </summary>
    public HotkeyHandler()
    {
      this.CreateHandle(new CreateParams());
      this._hotKeys = new Dictionary<int, HotKeyInfo>();
    }

    /// <summary>
    /// Registers the specified hot-key without an access modifier.
    /// </summary>
    /// <param name="key">The key of the hot-key.</param>
    /// <returns><strong>true</strong> is the registration succeded, <strong>false</strong> otherwise.</returns>
    public bool Register(Keys key)
    {
      return this.Register(key, Modifiers.None);
    }

    /// <summary>
    /// Registers the specified hot-key.
    /// </summary>
    /// <param name="key">The ke of the hot-key.</param>
    /// <param name="modifier">The access modifier of the hot-key.</param>
    /// <returns><strong>true</strong> is the registration succeded, <strong>false</strong> otherwise.</returns>
    public bool Register(Keys key, Modifiers modifier)
    {
      int id = _lastID + 1;

      if (RegisterHotKey(this.Handle, id, (uint)modifier, (uint)key) == true)
      {
        // If the registration succeeded, add the hot-key to the hash table so we can tell which is pressed later.
        _lastID = id;
        this._hotKeys.Add(_lastID, new HotKeyInfo(key, modifier));
        return true;
      }

      return false;
    }

    /// <summary>
    /// Unregisters the specified hot-key.
    /// </summary>
    /// <param name="key">The ke of the hot-key.</param>
    /// <param name="modifier">The access modifier of the hot-key.</param>
    public void Unregister(Keys key, Modifiers modifier)
    {
      HotKeyInfo hotKey = new HotKeyInfo(key, modifier);
      if (this._hotKeys.ContainsValue(hotKey))
      {
        foreach (int id in this._hotKeys.Keys)
        {
          if (this._hotKeys[id] == hotKey)
          {
            UnregisterHotKey(this.Handle, id);
            this._hotKeys.Remove(id);
            return;
          }
        }
      }
    }

    /// <summary>
    /// Unregister all the hot-keys.
    /// </summary>
    public void Unregister()
    {
      foreach (int id in this._hotKeys.Keys)
      {
        UnregisterHotKey(this.Handle, id);
      }

      this._hotKeys.Clear();
      _lastID = 0;
    }

    #region Dispose

    private void FreeResources()
    {
      try
      {
        this.Unregister();
        if (this.Handle != IntPtr.Zero)
        {
          this.DestroyHandle();
        }
      }
      catch
      {
      }
    }

    /// <summary>
    /// Releases unmanaged resources and performs other cleanup operations before the
    /// <see cref="HotkeyHandler"/> is reclaimed by garbage collection.
    /// </summary>
    ~HotkeyHandler()
    {
      this.FreeResources();
    }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
      this.FreeResources();
      GC.SuppressFinalize(this);
    }

    #endregion

    /// <summary>
    /// Invokes the default window procedure associated with this window.
    /// </summary>
    /// <param name="m">A <see cref="T:System.Windows.Forms.Message"></see> that is associated with the current Windows message.</param>
    protected override void WndProc(ref Message m)
    {
      if (m.Msg == HotkeyHandler.WM_HOTKEY)
      {
        // WParam contains the ID of the hot-key.
        int hID = m.WParam.ToInt32();
        if (this._hotKeys.ContainsKey(hID))
        {
          if (this.HotKeyPressed != null)
          {
            this.HotKeyPressed(this, new HotkeyPressedEventArgs(this._hotKeys[hID].Key, this._hotKeys[hID].Modifier));
          }
          else
          {
            base.WndProc(ref m);
          }
        }
        else
        {
          base.WndProc(ref m);
        }
      }
      else
      {
        base.WndProc(ref m);
      }
    }
  }
}
